using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Web;
using System.Web.UI;
using System.Reflection;
using Dean.Edwards;
using DNAide.Core;
using DNAide.Web.UI.Scripts.Minifier;

namespace DNAide.Web.UI.Scripts
{
	public sealed partial class ScriptAideNew
	{

		#region Static

		/// <summary></summary>
		private static ScriptAideContext CurrentContext
		{
			get
			{
				return (HttpContext.Current.Items["DNAideScriptAideContext"] ?? (
					HttpContext.Current.Items["DNAideScriptAideContext"] =
					new ScriptAideContext(HttpContext.Current.CurrentHandler as Page))) as ScriptAideContext;
			}
		}

		internal static string CacheUrl
		{
			get
			{
				return DNAide.Configuration.DNAideConfigurationSettings.Web.CacheUrl + "/Scripts";
			}
		}

		internal static string EnsureCachePhysicalLocationExists()
		{
			string retval = Path.Combine(DNAide.Configuration.DNAideConfigurationSettings.Web.CachePhysicalPath, "Scripts");
			if (!Directory.Exists(retval))
			{
				Directory.CreateDirectory(retval);
			}
			return retval;
		}

		public static RegisterLocationType DefaultLocationType
		{
			get { return ScriptAideNew.CurrentContext.DefaultLocationType; }
			set { ScriptAideNew.CurrentContext.DefaultLocationType = value; }
		}


		public static String DefaultLocationControlId
		{
			get { return ScriptAideNew.CurrentContext.DefaultLocationControlId; }
			set { ScriptAideNew.CurrentContext.DefaultLocationControlId = value; }
		}


		public static ObfuscateType DefaultObfuscateType
		{
			get { return ScriptAideNew.CurrentContext.DefaultObfuscateType; }
			set { ScriptAideNew.CurrentContext.DefaultObfuscateType = value; }
		}


		public static Boolean BatchIncludes
		{
			get { return ScriptAideNew.CurrentContext.BatchIncludes; }
			set { ScriptAideNew.CurrentContext.BatchIncludes = value; }
		}


		public static BatchedFileRegisterPositionType BatchFileRegisterPosition
		{
			get { return ScriptAideNew.CurrentContext.BatchFileRegisterPosition; }
			set { ScriptAideNew.CurrentContext.BatchFileRegisterPosition = value; }
		}


		public static void Register(ScriptDetails details)
		{
			ScriptAideNew.CurrentContext.AddScriptToRegister(details);
		}


		public static void RegisterInline(String script)
		{
			ScriptDetails details = new ScriptDetails();
			details.ScriptType = ScriptType.Inline;
			details.RegisterLocation = ScriptAideNew.DefaultLocationType;
			details.PlaceHolderId = ScriptAideNew.DefaultLocationControlId;
			details.Key = StringAide.ToHash("DNAideScriptAideInline" + script, HashAlgorithmType.MD5);
			details.Value = script;
			details.ObfuscateType = ScriptAideNew.DefaultObfuscateType;
			details.BatchInclude = false;
			ScriptAideNew.CurrentContext.AddScriptToRegister(details);
		}


		public static void RegisterInclude(String scriptUrl)
		{
			ScriptDetails details = new ScriptDetails();
			details.ScriptType = ScriptType.Include;
			details.RegisterLocation = ScriptAideNew.DefaultLocationType;
			details.PlaceHolderId = ScriptAideNew.DefaultLocationControlId;
			details.Key = StringAide.ToHash("DNAideScriptAideInclude" + scriptUrl, HashAlgorithmType.MD5); ;
			details.Value = scriptUrl;
			details.ObfuscateType = ScriptAideNew.DefaultObfuscateType;
			details.BatchInclude = ScriptAideNew.BatchIncludes;
			ScriptAideNew.CurrentContext.AddScriptToRegister(details);
		}


		public static void RegisterDNAideResource(DNAideScriptResource dnaideScriptResource)
		{
			ScriptDetails details = new ScriptDetails();
			details.ScriptType = ScriptType.DNAideResource;
			details.RegisterLocation = ScriptAideNew.DefaultLocationType;
			details.PlaceHolderId = ScriptAideNew.DefaultLocationControlId;
			details.Key = StringAide.ToHash("DNAideScriptAideResources" + dnaideScriptResource.ToString(), HashAlgorithmType.MD5); ;
			details.Value = dnaideScriptResource.ToString();
			details.ObfuscateType = ScriptAideNew.DefaultObfuscateType;
			details.BatchInclude = ScriptAideNew.BatchIncludes;
			ScriptAideNew.CurrentContext.AddScriptToRegister(details);
		}


		internal static String Minify(string script)
		{
			ScriptMinifier minifier = new ScriptMinifier();	
			return minifier.Minify(script);
		}

		internal static String Pack(string script)
		{
			ECMAScriptPacker packer = new ECMAScriptPacker(ECMAScriptPacker.PackerEncoding.Numeric, true, false);
			return packer.Pack(script);
		}

		public static String Obfuscate(string script, ObfuscateType obfuscateType)
		{
			script = CastAide.AsString(script);
			switch (obfuscateType)
			{
				case ObfuscateType.Minify:
					script = ScriptAideNew.Minify(script);
					break;
				case ObfuscateType.Pack:
					script = ScriptAideNew.Pack(script);
					break;
			}
			return script;
		}

		#endregion

	}


	internal class ScriptAideContext
	{
		private object _lock = new object();
		private Page _page;
		private ObfuscateType _defaultObfuscateType;
		private RegisterLocationType _defaultLocationType;
		private string _defaultLocationControlId;
		private bool _batchIncludes;
		private BatchedFileRegisterPositionType _batchFileRegisterPosition;
		private Dictionary<string, Dictionary<string, ScriptDetails>> _scriptsToRegister;

		internal ObfuscateType DefaultObfuscateType
		{
			get { return this._defaultObfuscateType; }
			set { this._defaultObfuscateType = value; }
		}

		internal RegisterLocationType DefaultLocationType
		{
			get { return this._defaultLocationType; }
			set { this._defaultLocationType = value; }
		}

		internal string DefaultLocationControlId
		{
			get { return this._defaultLocationControlId; }
			set { this._defaultLocationControlId = value; }
		}

		internal bool BatchIncludes
		{
			get { return this._batchIncludes; }
			set { this._batchIncludes = value; }
		}

		internal BatchedFileRegisterPositionType BatchFileRegisterPosition
		{
			get { return this._batchFileRegisterPosition; }
			set { this._batchFileRegisterPosition = value; }
		}

		internal ScriptAideContext(Page page)
		{
			this._page = page;
			this._page.PreRenderComplete += new EventHandler(_page_PreRenderComplete);

			this._defaultObfuscateType = ObfuscateType.None;
			this._defaultLocationType = RegisterLocationType.Head;
			this._defaultLocationControlId = null;
			this._batchIncludes = false;
			this._batchFileRegisterPosition = BatchedFileRegisterPositionType.AfterInlineScripts;
			this._scriptsToRegister = new Dictionary<string, Dictionary<string, ScriptDetails>>();
		}

		void _page_PreRenderComplete(object sender, EventArgs e)
		{
			foreach (string locationKey in this._scriptsToRegister.Keys)
			{
				Control registerControl;
				switch (locationKey)
				{
					case "DNAideScriptAideForm":
						registerControl = this._page.Form;
						break;
					case "DNAideScriptAideHead":
						registerControl = this._page.Header;
						break;
					default:
						registerControl = this._page.FindControl(locationKey);
						break;
				}
				if (registerControl == null)
				{
					throw new Exception("Cannot find control to register scripts.");
				}


				StringBuilder sb = new StringBuilder();
				List<string> filesToBatch = new List<string>();

				StringBuilder batchFileName = new StringBuilder();

				foreach (KeyValuePair<string, ScriptDetails> kvp in this._scriptsToRegister[locationKey])
				{
					string cacheUrlFileName, cachePhysicalFileName;

					switch (kvp.Value.ScriptType)
					{
						case ScriptType.DNAideResource:
							HttpContext.Current.Response.Write(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);
							break;
						
						case ScriptType.Include:
							// Register include
							// Take a copy and move it into the cache if applicable
							string originalPhysicalFileName = HttpContext.Current.Server.MapPath(kvp.Value.Value);
							if (!File.Exists(originalPhysicalFileName))
							{
								throw new Exception(string.Format("Unable to locate js include file {0}.", originalPhysicalFileName));
							}
							cacheUrlFileName = string.Format("{0}/{1}.js", ScriptAideNew.CacheUrl, StringAide.ToHash(kvp.Value.Value + File.GetLastWriteTime(originalPhysicalFileName).Ticks.ToString(), HashAlgorithmType.MD5)); 
							cachePhysicalFileName = HttpContext.Current.Server.MapPath(cacheUrlFileName);

							// Create the physical file in the cache
							if (!File.Exists(cachePhysicalFileName))
							{
								lock (this._lock)
								{
									if (!File.Exists(cachePhysicalFileName))
									{
										ScriptAideNew.EnsureCachePhysicalLocationExists();
										File.WriteAllText(cachePhysicalFileName, ScriptAideNew.Obfuscate(CastAide.AsString(File.ReadAllText(originalPhysicalFileName)), kvp.Value.ObfuscateType));
									}
								}
							}

							if (kvp.Value.BatchInclude)
							{
								filesToBatch.Add(cachePhysicalFileName);
								batchFileName.Append(cacheUrlFileName);
								batchFileName.Append(File.GetLastWriteTime(cachePhysicalFileName).Ticks);
							}
							else
							{
								sb.AppendFormat("\r\n<script type=\"text/javascript\" src=\"{0}\"></script>\r\n", this._page.ResolveUrl(cacheUrlFileName));
							}
							break;

						case ScriptType.Inline:
							// Register Inline Script
							sb.AppendFormat("\r\n<script type=\"text/javascript\">\r\n//<![CDATA[\r\n{0}\r\n//]]>\r\n</script>\r\n", ScriptAideNew.Obfuscate(kvp.Value.Value, kvp.Value.ObfuscateType));
							break;

					}
				}

				if (batchFileName.Length > 0)
				{
					string hashedBatchFileName = StringAide.ToHash(batchFileName.ToString(), HashAlgorithmType.MD5) + ".js";

					string hashedBatchFileNameUrlPath = string.Format("{0}/{1}.js", ScriptAideNew.CacheUrl, hashedBatchFileName);
					string hashedBatchFileNamePhysicalPath = HttpContext.Current.Server.MapPath(hashedBatchFileNameUrlPath);
					if (!File.Exists(hashedBatchFileNamePhysicalPath))
					{
						lock (this._lock)
						{
							if (!File.Exists(hashedBatchFileNamePhysicalPath))
							{
								ScriptAideNew.EnsureCachePhysicalLocationExists();
								StringBuilder batchedFileContent = new StringBuilder();
								foreach (string fileToBatch in filesToBatch)
								{
									File.AppendAllText(hashedBatchFileNamePhysicalPath, File.ReadAllText(fileToBatch));
								}
							}
						}
					}

					if (this._batchFileRegisterPosition == BatchedFileRegisterPositionType.BeforeInlineScripts)
					{
						sb.Insert(0, string.Format("\r\n<script type=\"text/javascript\" src=\"{0}\"></script>\r\n", this._page.ResolveUrl(hashedBatchFileNameUrlPath)));
					}
					else
					{
						sb.AppendFormat("\r\n<script type=\"text/javascript\" src=\"{0}\"></script>\r\n", this._page.ResolveUrl(hashedBatchFileNameUrlPath));
					}
				}

				LiteralControl lit = new LiteralControl(sb.ToString());
				registerControl.Controls.Add(lit);
			}
		}


		internal void AddScriptToRegister(ScriptDetails details)
		{
			if (details == null)
			{
				throw new ArgumentException("Null ScriptDetails passed");
			}

			string locationKey;
			switch (details.RegisterLocation)
			{
				case RegisterLocationType.Form:
					locationKey = "DNAideScriptAideForm";
					break;
				case RegisterLocationType.Head:
					locationKey = "DNAideScriptAideHead";
					break;
				default:
					locationKey = details.PlaceHolderId;
					break;
			}

			if (string.IsNullOrEmpty(locationKey))
			{
				throw new ArgumentException("PlaceHolder id required.");
			}

			if (string.IsNullOrEmpty(details.Key))
			{
				throw new ArgumentException("Key required.");
			}

			if (!this._scriptsToRegister.ContainsKey(locationKey))
			{
				this._scriptsToRegister.Add(locationKey, new Dictionary<string, ScriptDetails>());
			}
			if (!this._scriptsToRegister[locationKey].ContainsKey(details.Key))
			{
				this._scriptsToRegister[locationKey].Add(details.Key, details);
			}
		}

	}


	public class ScriptDetails
	{

		private ScriptType _scriptType;
		private RegisterLocationType _registerLocation;
		private string _placeHolderId;
		private string _key;
		private string _value;
		private ObfuscateType _obfuscateType;
		private bool _batchInclude;

		public ScriptType ScriptType
		{
			get { return this._scriptType; }
			set { this._scriptType = value; }
		}

		public RegisterLocationType RegisterLocation
		{
			get { return this._registerLocation; }
			set { this._registerLocation = value; }
		}

		public String PlaceHolderId
		{
			get { return this._placeHolderId; }
			set { this._placeHolderId = value; }
		}

		public String Key
		{
			get { return this._key; }
			set { this._key = value; }
		}

		public String Value
		{
			get { return this._value; }
			set { this._value = value; }
		}

		public ObfuscateType ObfuscateType
		{
			get { return this._obfuscateType; }
			set { this._obfuscateType = value; }
		}

		public Boolean BatchInclude
		{
			get { return this._batchInclude; }
			set { this._batchInclude = value; }
		}

		public ScriptDetails()
		{
		}

	}



	public enum ObfuscateType
	{
		None = 1,
		Minify = 2,
		Pack = 3
	}


	public enum ScriptType
	{
		Inline = 1,
		Include = 2,
		DNAideResource = 3
	}


	public enum RegisterLocationType
	{
		Head = 1,
		Form = 2,
		PlaceHolder = 3
	}

	public enum BatchedFileRegisterPositionType
	{
		BeforeInlineScripts = 1,
		AfterInlineScripts = 2
	}

}
